package gbench.appdemo.crawler.junit.app;

import org.junit.jupiter.api.Test;

import java.io.File;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Stream;

import gbench.common.fs.FileSystem;
import gbench.appdemo.crawler.CrawlerApplication;

import static gbench.appdemo.crawler.CrawlerApplication.*;
import static gbench.appdemo.crawler.srch.AbstractJdbcSrchEngine.*;
import static gbench.appdemo.crawler.srch.SrchUtils.*;
import static gbench.common.tree.LittleTree.IRecord.*;
import static gbench.common.tree.LittleTree.*;


/**
 * 
 * Lucene 的基本概念的说明 <br> 
 * 
 * Lucene 的核心是一个 数据库系统，但 很明显 他不是一个 关系数据库，他是一个 多维数据集(Index)的数据库。<br> 
 * 这个多维数据集的 基本单元 被称为 Document即索引记录/文档。索引记录这个名称不好理解，但你就把它当做 是堆砌成 Index索引库 这栋大厦最为基本的砖块 就可以了：他是一个这样的 逻辑。<br> 
 * Index = [Document1, Document2, Document3, ... ] <br> 
 * 一个Index 通过 IndexReader 这个界面来 进行访问，依旧是 可以通过 IndexReader 来遍历这个 Document 序列。<br> 
 *  Document 是由Field构成的，Field 拥有 两个维度(存储结构:维度 和  索引分词维度）<br> 
 *  存储结构：即field 是作为独立的Document成员保存于Document。即检索出的Document中是否包含由该field字段。就像 演戏 一样,有前台人员,幕后人员。field 也存在有类似
 *  的概念：存储式 和 非存储式<br> 
 *  1) 存储式 就前台，后台 都包含，即公开这个field的原始数据。对可以在 检索结果document中 获取到 field 信息。<br> 
 *  2) 非存储式 则是仅作为后台形式出现，不在再Document中的 登台形式（查询结果）里出现。或者就是 就是不公开这个field的原始数据。 对 该 field 检索，获取不到该field的原始信息。而只能
 *  获得document(即其他存储式field)的西悉尼。这个有点类似于：空气。隐形的存在，可以被检索查询但是你就是看不到<br> 
 *  
 *  索引分词： 就是为field制作出一种查询维度(lucene把一个由查询维度构成结构称为query，再查询Query中，用TermQuery来定义查询维度，而TermQuery需要根据Field即索引分词来给予定义)。<br> 
 *  即 document按照field这个维度进行查询 需要如何制作准备 查询结构(即索引).<br> 
 *  索引这里就是 制作查询结构（即索引）的意思，分词就是把 信息元 给分解成最小的语义成分单词元 token. 索引分词就是 如何为field 制作 检索结构 的意思。这里包括两种方式：<br> 
 *  1) 分词：即 信息元 是一个 多token的结构，需要通过analyzer来分词成更小的 语义单元，然后 根据token来制作 （即索引）的意思。 <br> 
 *  2) 不分词：即 信息元 本身就是 最小的 语义成分单词元 token。不予进行更进一步的 分词。 <br> 
 *  
 * @author gbench
 *
 */
public class JunitCrawlerAppDemo {
    
    /**
     * 表示一个可以抛出异常的函数操作的函数接口 <br>
     * @author gbench
     *
     */
    @FunctionalInterface
    interface MaybeThrowable{
        public void run() throws Throwable;
    }
    
    /**
     * 这个函数被用于简化 try catch 的程序辨析。自动铺货异常。采用如下的方法就可以简写系统异常了。<br>
     * trycatch(()->{....}); <br>
     * 对一个可以能抛出异常的操作进行异常捕获，并给与执行。<br>
     * @param maybe 可能抛出异常的操作
     */
    public static void trycatch(final MaybeThrowable maybe) {
        try {
            maybe.run();
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 样式程序的标注接口
     * @author gbench
     *
     */
    interface IDemo{
        /**
         * 获取demo程序的名称
         * @return demo程序的名称
         */
        public default String getName() {return this.getClass().getName();};
        
        /**
         * 创建索引
         */
        public default void index() {};
        
        /**
         * 检索索引
         */
        public default void query() {};
    }// IDemo
    
    /**
     * 演示环境1
     * @author gbench
     *
     */
    public class Demo1 implements IDemo{
        /**
         * 创建索引
         */
        public void index() {
            final var engine = app.new FileSrchEngine();
            engine.initialize();
            app.indexFiles("C:\\Users\\gbench\\Desktop\\中国笔记\\逻辑散文\\novel\\archive", status->{
                System.out.println(status);
            });
        }// index
        
        /**
         * 索引操作
         */
        public void query() {
            final var engine = app.new FileSrchEngine();
            engine.initialize();
            var rec =REC("+symbol","所以","+file","围城第一章.txt"); // 检索条件
            engine.lookup(rec.collect(bool_query_clc), 100).stream()
            .forEach(System.out::println);
        }// query
       
    }// Demo1
    
    /**
     * 
     * @author gbench
     *
     */
    public class Demo2 implements IDemo {
        /**
         * 创建索引
         */
        public void index() {
            final var engine = app.new FileSrchEngine();
            engine.initialize();
            engine.writeIndexes(writer->{
                traverse("C:\\Users\\gbench\\Desktop\\中国笔记\\逻辑散文\\novel\\archive", file->{
                    final var counter = new AtomicLong(1); // 计数器
                    FileSystem.utf8lines(file).forEach(line->{
                        trycatch(()->{
                            final var rec = REC(
                                "rownum",counter.getAndIncrement(), // 行号
                                "$name",line, // 对line进行分词,以便可以 实现通过部分找整体,即管中窥豹的能力
                                "file",file.getName() // 文件名称
                            ); // 找出分词所在的行号
                            writer.addDocument(rec2doc(rec));
                        });// 省略trycatch
                    });// forEach
                });// forEach
            });// writeIndexes
        }// index
        
        /**
         * 查询操作
         */
        @Override
        public void query() {
            final var engine = app.new FileSrchEngine();
            engine.initialize();
            
            System.out.println("---------------检索红海出现在那些name字段中:管中窥豹,一个点：红海----------------------");
            /**
             *  查询说明
             *  
             *  对name字段进行精准匹配.由于对name 已经做了分词。这里的 精准匹配是说明 分词后的精准配置，
             *  而不是整个name字段做精准匹配，只要输入name 中的任何一个有效分词，就都可以检索出 name字段来。
             */
            final var rec = REC("+name*","红海","+file","围城第一章.txt"); 
            engine.lookup(rec.collect(bool_query_clc), 10).stream()
            .forEach(System.out::println);
            
            System.out.println("---------------检索以红开头的词出现在哪些些name字段中:管中窥豹，多个点：红*----------------------");
            
            /**
             *  查询说明
             *  
             *  对name字段进行通配符匹配.由于对name 已经做了分词。这由于是通配符匹配。‘红*’表示对
             *  检索含有红开头的词的所在的name字段
             *  
             */
            final var rec2 = REC("+name*","红*","+file","围城第一章.txt"); 
            engine.lookup(rec2.collect(bool_query_clc), 10).stream()
            .forEach(System.out::println);
            
            System.out.println("---------------打印全部索引记录----------------------");
            /**
             *  查询说明
             *  
             *  对name字段进行通配符匹配.由于对name 已经做了分词。这由于是通配符匹配。‘*’把索引
             *  中的记录name字段全部提取出来。
             *  
             */
            final var rec3 = REC("+name*","*","+file","围城第一章.txt"); 
            engine.lookup(rec3.collect(bool_query_clc), 10).stream()
            .forEach(System.out::println);
        }// query
        
    }// Demo2
    
    /**
     * 演示  
     * 通过 line 去查询 rownum 与 file 但不对 line 进行存储。
     * @author gbench
     *
     */
    public class Demo3 implements IDemo{

        @Override
        public void index() {
            final var engine = app.new FileSrchEngine();
            engine.initialize();
            engine.writeIndexes(writer->{
                traverse("C:\\Users\\gbench\\Desktop\\中国笔记\\逻辑散文\\novel\\archive", file->{
                    final var counter = new AtomicLong(1);// 计数器
                    FileSystem.utf8lines(file).forEach(line->{
                        trycatch(()->{
                            final var rec = REC(
                                "rownum",counter.getAndIncrement(), // 行号
                                "$line?",line, // 对line进行分词,以便可以 实现通过部分找整体,即管中窥豹的能力,单不对 line进行存储
                                "file",file.getName() // 文件名称
                            ); // 找出分词所在的行号
                            writer.addDocument(rec2doc(rec));
                        });// 省略trycatch
                    });// forEach
                });// forEach
            });// writeIndexes
            
        }// index

        @Override
        public void query() {
            final var engine = app.new FileSrchEngine();
            engine.initialize();
            final var rec = REC("+line*","红海"); 
            engine.lookup(rec.collect(bool_query_clc), 10).forEach(e->{
                System.out.println(e);
            });
        }// query
        
    }// Demo3
    
    /**
     * 传统的关系型数据库的查询
     * @author gbench
     *
     */
    public class Demo4 implements IDemo{
        
        //@Override
        public void index() {
            final var engine = app.new FileSrchEngine();
            //engine.initialize();
            engine.withTransaction(sess->{
                Stream.iterate(0, i->i+1).limit(100).forEach(i->{
                   sess.sqlupdate(T("id",i),REC("id?",i,"code",i,"name","zhang"+i));
                });//Stream forEach
            });//writeIndexes2
        }//index

        //@Override
        public void query() {
            final var engine = app.new FileSrchEngine();
            //engine.initialize();
            final var id = 98;// 操作  索引文档 98号
            System.out.println("\n按照id查询");
            engine.withTransaction(sess->{
                final var old = sess.sqlget1(REC("id",id));// 查询出来原来的值
                System.out.println("old:"+old);
                sess.sqlupdate(T("id",id), old.derive("id?",id, // 添加不可见主键,以保证 后续的查询可以继续采用 id 进行检索
                    "name","lisi","friend","赵六"),true);// 注意在修改的时候,主动附加了id? 项目作为 不可见的主键。
                final var update = sess.sqlget1(REC("id",id));// 查询出来原来的值
                System.out.println("update:"+update);
                sess.sqlremove(REC("id",97));// 把97号元素给予删除掉
            });// 索引操作会话。
            
            // 遍历所有文档
            System.out.println("遍历文档");
            engine.docStream2().forEach(System.out::println);
        }//query
        
    }// Demo4
    
    
    /**
     * 分页查询的演示，这就更有点向关系数据库了
     * @author gbench
     *
     */
    public class Demo5 implements IDemo{
        
        //@Override
        public void index() {
            final var engine = app.new FileSrchEngine();
            //engine.initialize();
            engine.withTransaction(sess->{
                Stream.iterate(0, i->i+1).limit(300).forEach(i->{
                   sess.sqlupdate(T("id",i),REC("id?",i,"code",i,"name","zhang"+i,"address",(i%2==0?"shanghai":"beijing")+i));
                });//Stream forEach
            });//writeIndexes2
        }//index

        //@Override
        public void query() {
            final var engine = app.new FileSrchEngine();
            final var pageQuery = engine.getPageQuery(Q("id*","*"));// 创建一个页面查询对象
            pageQuery.initialize(100,L("name,address".split(",")));
            trycatch(()->{
                
                var optional = pageQuery.getData();
                int pageNum = 0;
                while(optional.isPresent()) {
                    System.out.println(MFT("\n 第 #{0} 页:",pageNum));// 显示页号
                    final var pageData = optional.get();
                    pageData.forEach(System.out::println);
                    optional = pageQuery.nextPage();
                    pageNum++;
                }//while
                
                System.out.println("-----向前滚动-----");
                optional = pageQuery.prevPage();
                while(optional.isPresent()) {
                    System.out.println(MFT("\n 第 #{0} 页:",pageNum));// 显示页号
                    final var page = optional.get();
                    page.forEach(System.out::println);
                    optional = pageQuery.prevPage();
                    pageNum--;
                }//while
                
                System.out.println("-----流式向后滚动-----");
                pageQuery.hitsStream().forEach(doc->{
                    System.out.println(doc);
                });
                
                System.out.println("-----流式向前滚动-----");
                pageQuery.hitsStream2().forEach(doc->{
                    System.out.println(doc);
                });
                
            });// trycatch
        }//query
        
    }// Demo5
    
    /**
     * 测试启动函数
     */
    @Test
    public void demo() {
        Stream.of(
            //this.new Demo1(),
            //this.new Demo2(),
            //this.new Demo3(),
            //this.new Demo4(),
            this.new Demo5()
        ).forEach(demo->{
            System.out.println(MFT("\n------现在开始运行 {0}------",demo.getName()));
            traverse(new File(indexHome),File::delete);// 删除原来的文件
            
            System.out.println(MFT("步骤 {0}) 创建索引文件",1));
            demo.index();
            
            System.out.println(MFT("步骤 {0}) 执行文件查询",2));
            demo.query();
        });
       
    }
    
    public String indexHome = "D:\\sliced\\tmp\\myluceneapp\\indexes";// 索引文件的路径
    public String corpusHome = "C:\\Users\\gbench\\Desktop\\中国笔记\\逻辑词汇";// 语料库的词汇目录
    public String snapHome = "D:\\sliced\\tmp\\myluceneapp\\snapfiles";// 快照文件根目录
    final CrawlerApplication app = new CrawlerApplication(indexHome, corpusHome,snapHome);

}
